// Copyright 2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: Gunnar Ritter
//
// A command line frontend for the POP/IMAP troubleshooter, mainly for
// development and for use within scripts.

#include "cmdline/cmdline.h"

#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#include <errno.h>
#include <QCoreApplication>
#include <QStringList>

#include "util/provider.h"
#include "probes/executive.h"

namespace popdiag {

class Provider *provider;

};

namespace popdiag_cmdline {

// Print this string whenever the previous percentage must be erased.
static const char kEraseProgressIndicator[] = "\r        \r";

Cmdline::Cmdline(int argc, char **argv)
    : executive_(NULL),
      quiet_(false),
      output_(stdout),
      stderr_(stderr),
      argc_(argc),
      argv_(argv) {
}

Cmdline::~Cmdline() {
  delete executive_;
  if (output_ != stdout && output_ != NULL)
    fclose(output_);
  if (stderr_ != stderr && stderr_ != NULL)
    fclose(stderr_);
}

void Cmdline::OnProgressMade(int progress_made, int estimated_total) {
  if (!quiet_)
    fprintf(stderr_, "%s% 3.02f%%", kEraseProgressIndicator,
            progress_made * 100.0 / estimated_total);
}

// Retrieves the report from the Executive object and prints it to
// standard output or the file given with the -o option.
void Cmdline::OnReportReady() {
  fputs(executive_->GetReport().toUtf8(), output_);
}

// Exits the application after everything is done.
void Cmdline::OnExecutiveDone() {
  if (!quiet_)
    fprintf(stderr_, "%sDone.\n", kEraseProgressIndicator);
  exit(0);
}

// This is mainly provided for the unit test, which calls this
// method with "/dev/null".
void Cmdline::SetErrorFile(QString name) {
  stderr_ = fopen(qPrintable(name), "a");
}

// Parses the options, handles errors (if any), sets up the Executive
// object, and starts it to run the probes.
int Cmdline::Start() {
  popdiag::provider = popdiag::InitProvider();
  CmdlineOptions cmdlineoptions;
  if (!cmdlineoptions.Getopt(argc_, argv_)) {
    Usage(cmdlineoptions.progname(), cmdlineoptions.error());
    return EXIT_CODE_SYNTAX_ERROR;
  }
  if (cmdlineoptions.username() == "") {
    Usage(cmdlineoptions.progname(), tr("No username specified"));
    return EXIT_CODE_SYNTAX_ERROR;
  }
  if (cmdlineoptions.password() == "") {
    Usage(cmdlineoptions.progname(), tr("No password specified"));
    return EXIT_CODE_SYNTAX_ERROR;
  }
  if (cmdlineoptions.output() != "") {
    output_ = fopen(qPrintable(cmdlineoptions.output()), "w");
    if (output_ == NULL) {
      Error(cmdlineoptions.progname(),
            tr("Failed to open %1 for writing: %2")
            .arg(cmdlineoptions.output())
            .arg(strerror(errno)));
      return EXIT_CODE_RUNTIME_FAILURE;
    }
  }
  quiet_ = cmdlineoptions.quiet() || cmdlineoptions.debug() ||
      !isatty(STDERR_FILENO);
  popdiag::Probe::PrintDebuggingInformation(cmdlineoptions.debug());
  // The ExecutiveOptions structure is normally used as glue
  // between the GUI and the code that actually executes the
  // probes. We reuse this mechanism in order to call the
  // probes from the commandline frontend.
  popdiag::ExecutiveOptions eoptions;
  eoptions.username = cmdlineoptions.username();
  eoptions.password = cmdlineoptions.password();
  eoptions.imap_server = cmdlineoptions.imap_server();
  eoptions.pop_server = cmdlineoptions.pop_server();
  eoptions.smtp_server = cmdlineoptions.smtp_server();
  eoptions.smtp_port = cmdlineoptions.smtp_port();
  eoptions.config_log = "";
  eoptions.pull_all_mail = cmdlineoptions.pull_all_mail();
  executive_ = new popdiag::Executive(NULL, eoptions);
  // Sets up callbacks that allow to display progress, similar
  // to what the GUI would normally do.
  CHECK(connect(executive_, SIGNAL(ProgressMade(int, int)),
                this, SLOT(OnProgressMade(int, int))));
  CHECK(connect(executive_, SIGNAL(ReportReady()),
                this, SLOT(OnReportReady())));
  CHECK(connect(executive_, SIGNAL(Done()),
                this, SLOT(OnExecutiveDone())));
  executive_->Start();
  return 0;
}

void Cmdline::Error(QString progname, QString error) {
    fprintf(stderr_, "%s: %s\n", qPrintable(progname), qPrintable(error));
}

void Cmdline::Usage(QString progname, QString error) {
  Error(progname, error);
  Error(progname, tr("Usage: %1 -u username -w password "
                     "[-i imap_server] [-p pop_server] "
                     "[-s smtp_server] [-S smtp_port] "
                     "[-o output.html] [-adq]")
                  .arg(progname));
}

// CmdlineOptions class - calls getopt_long() and makes the
// values accessible.

CmdlineOptions::CmdlineOptions()
  : error_(""), username_(""), password_(""),
    imap_server_("imap.gmail.com"), pop_server_("pop.gmail.com"),
    smtp_server_("smtp.gmail.com"), smtp_port_(popdiag::SMTP_MSA_SSL_PORT),
    output_(""), pull_all_mail_(false), quiet_(false), debug_(false) {
}

bool CmdlineOptions::Getopt(int argc, char **argv) {
  CHECK_GT(argc, 0);
  progname_ = basename(argv[0]);
  const struct option long_options[] = {
    { "username",      required_argument, NULL, 'u' },
    { "password",      required_argument, NULL, 'w' },
    { "imap_server",   required_argument, NULL, 'i' },
    { "pop_server",    required_argument, NULL, 'p' },
    { "smtp_server",   required_argument, NULL, 's' },
    { "smtp_port",     required_argument, NULL, 'S' },
    { "output",        required_argument, NULL, 'o' },
    { "pull_all_mail", no_argument,       NULL, 'a' },
    { "quiet"        , no_argument,       NULL, 'q' },
    { "debug"        , no_argument,       NULL, 'd' },
    { NULL,            0,                 NULL, 0   }
  };
  int option_index = 0;
  int c;
  // Resets getopt_long(), mainly for unit tests where we call
  // it multiple times in one application.
  optind = 0;
  while ((c = getopt_long(argc, argv, ":u:w:i:p:s:S:o:aqd", long_options,
                          &option_index)) != -1) {
    switch (c) {
      case 'u':
        username_ = optarg;
        break;
      case 'w':
        password_ = optarg;
        break;
      case 'i':
        imap_server_ = optarg;
        break;
      case 'p':
        pop_server_ = optarg;
        break;
      case 's':
        smtp_server_ = optarg;
        break;
      case 'S': {
        char *endptr;
        smtp_port_ = strtol(optarg, &endptr, 10);
        if (*endptr) {
          error_ = tr("Non-numeric argument for the POP port");
          return false;
        }
        break;
      }
      case 'o':
        output_ = optarg;
        break;
      case 'a':
        pull_all_mail_ = true;
        break;
      case 'q':
        quiet_ = true;
        break;
      case 'd':
        debug_ = true;
        break;
      case ':':
        error_ = tr("option requires an argument");
        if (optopt)
          error_.append(tr(" -- %1").arg(QChar(optopt)));
        return false;
      case '?':
        error_ = tr("illegal option");
        if (optopt)
          error_.append(tr(" -- %1").arg(QChar(optopt)));
        return false;
      default:
        CHECK(false);
    }
  }
  if (optind < argc) {
    error_ = tr("excess argument -- %1").arg(argv[optind]);
    return false;
  }
  return true;
}

}  // namespace popdiag_cmdline
